const jwt = require("jsonwebtoken");
const _ = require("lodash");
const path = require("path");
const yn = require("yn");
const vm = require("vm");
const md5 = require("md5");
const uuid = require("uuid");
const moment = require("moment");
const config = require("./../config");
const pkg = require("./../package.json");
const sandbox = require("./sandbox");
const util = require("./util");
const Query = require("./query");

module.exports = (ctx, next) => {
  return {
    /**
     * 处理关联关系
     * @param {*} table
     * @param {*} related
     */
    withRelateds(table, related) {
      const relateds = [];
      for (const key in related) {
        relateds.push({
          [key]: qb => {
            new Query(ctx, qb, related[key]);
          }
        });
      }
      return relateds;
    },

    /**
     * 处理字段
     * @param {*} className
     * @param {*} table
     * @param {*} fields
     */
    withFields(className, table, fields) {
      const _fields = {};
      for (const field of ctx.classFields) {
        if (field.required) {
          if (!_.has(fields, field.name)) {
            throw new Error(
              `Class ${className} Table ${table} Field ${
                field.name
              } Unauthorized`
            );
          }
        }

        if (field.url) {
          if (field.url !== ctx.url) {
            continue;
          }
        }

        if (field.value) {
          _fields[field.name] = field.value;
          continue;
        }

        if (field.type) {
          switch (field.type) {
            case "md5":
              if (_.has(fields, field.name)) {
                _fields[field.name] = md5(_.toString(fields[field.name]));
                delete fields[field.name];
              }
              break;
            case "bcrypt":
              if (_.has(fields, field.name)) {
                _fields[field.name] = util.bcrypt(
                  _.toString(fields[field.name])
                );
                delete fields[field.name];
              }
              break;
            case "uuid":
              _fields[field.name] = uuid();
              break;
            case "uuid.v1":
              _fields[field.name] = uuid.v1();
              break;
            case "uuid.v4":
              _fields[field.name] = uuid.v4();
              break;
            case "uuid.v5":
              _fields[field.name] = uuid.v5();
              break;
            case "moment":
              _fields[field.name] = moment();
              break;
          }
        }
      }

      for (const key in _fields) {
        const val = _.get(ctx.auth, _fields[key]);
        if (val) {
          _fields[key] = val;
        }
      }

      return _fields;
    },

    /**
     * 云函数
     * @param {*} body
     * @param {*} opts
     */
    async runInVm(body, opts = {}) {
      const globals = await BaaS.Models.global
        .query({
          where: {
            baas_id: ctx.baas.id
          }
        })
        .fetchAll({
          withRedisKey: ctx.getAppKey("global")
        });

      let bodys = "";
      for (const key in globals) {
        bodys += globals[key].body;
      }

      return vm.runInNewContext(
        `(async() => {
            ${bodys}
            ${body}
          })()
          `,
        Object.assign(opts, sandbox(ctx, next), ctx.baas.private ? { BaaS } : {}),
        {
          timeout: 3000
        }
      );
    },

    /**
     * 获取数据表model
     * @param {*} table
     * @param {*} database
     */
    model(table, database) {
      ctx.bookshelf = Object.assign({}, BaaS.bookshelf);

      // 私有项目，支持跨服务器查询
      if (!_.isEmpty(ctx.baas.connection)) {
        if (BaaS.bookshelfs[ctx.baas.appid]) {
          ctx.bookshelf = BaaS.bookshelfs[ctx.baas.appid];
        } else {
          ctx.bookshelf = BaaS.getBookshelf({
            client: "mysql",
            connection: ctx.baas.connection,
            pool: {
              min: 0,
              max: 1,
              idleTimeoutMillis: 300000 // pool max idle time
            }
          });
          BaaS.bookshelfs[ctx.baas.appid] = ctx.bookshelf;
        }
      }

      // 私有项目，支持跨数据库查询
      if (database) {
        database = ctx.baas.private ? database : ctx.baas.database;
      } else {
        database = ctx.baas.database;
      }

      return util.getModel(
        ctx.appid,
        ctx.appkey,
        ctx.dev,
        database,
        table,
        ctx.tables,
        ctx.relations,
        ctx.bookshelf
      );
    },

    /**
     * 记录日志到控制台
     * @param {*} message
     */
    log(message) {
      // 发布订阅
      BaaS.redis.publish(
        util.getVersionKey("log"),
        JSON.stringify(
          Object.assign(ctx.baas, {
            message: util.isJsonToStr(message) ? message : "Format unsupported"
          })
        )
      );
    },

    /**
     * 获取header, query键
     * @param {*} key
     */
    getKey(key) {
      return ctx.get(key) || ctx.query[key];
    },

    /**
     * 获取应用redis键
     * @param {*} key
     * @param {*} ignoreDev 忽略开发者
     */
    getAppKey(key, ignoreDev = false) {
      // 开发者模式，不缓存redis
      if (yn(ctx.dev) && !ignoreDev) {
        return "";
      }
      return util.getVersionKey(
        `appid:${ctx.appid}:appkey:${ctx.appkey}:${key}`
      );
    },

    /**
     * 获取应用文件存储目录
     */
    getAppDir(src) {
      return path.resolve(util.config("webDir"), ctx.baas.appid, src);
    },

    /**
     * [checkClassAuth description]
     * @param  {[type]} className [description]
     * @return {[type]}           [description]
     */
    async checkClassAuth(className) {
      const authInfo = {};
      for (const auth of ctx.class.auth) {
        const authToken = ctx.getKey(auth.name);
        if (!authToken) {
          ctx.status = 401;
          throw new Error(
            `Class ${className} AuthName ${auth.name} Unauthorized`
          );
        } else {
          try {
            const decoded = jwt.verify(authToken, auth.secret);
            authInfo[auth.name] = decoded;
          } catch (err) {
            ctx.status = 401;
            throw new Error(`AuthName ${auth.name} Unauthorized`);
          }
        }
      }
      
      return authInfo;
    },

    /**
     * [checkClassTable description]
     * @param  {[type]} className [description]
     * @param  {[type]} table [description]
     * @return {[type]}           [description]
     */
    checkClassTable(className, table) {
      const tables = util.strToArray(ctx.class.tables);
      if (tables.indexOf(table) === -1) {
        ctx.status = 401;
        throw new Error(`Class ${className} Table ${table} Unauthorized`);
      }
    },

    json(data, msg, code, meta) {
      ctx.body = {
        data,
        msg,
        code,
        meta
      };
    },

    success(data, msg, meta) {
      ctx.json(data, msg, 1, meta);
    },

    error(data, msg, meta) {
      ctx.json(data, msg, 0, meta);
    }
  };
};
